/*

 Audiometria
 Version 0.1
 
 Audiometry: hearing test, equal loudness contours (hearing thresholds), 
 sensitivity and acuity.
 
 Warning 1:
 Start with the master sound low!
    Loud sounds can hurt or damage your ears.
    I do not give any warranty nor take any responsibility about any bad usage
        of this software.
 
 
 Warning 2:
 This test does not, in any manner, substitute a doctor!
    This software is for informational purposes only.
    You can use to compare your left and right ears or to equalize your computer.
    The spectral purity and the sound level depend on your sound card and 
        headphones.
    Daily fluctuations in hearing are common.
    If you are worried about your listening, consult an audiologist.

 
 Copyright © 2012 Marcos Assis
 (except when mentioned)
 
 This program is free software: you can redistribute it and/or modify it under 
 the terms of the GNU General Public License as published by the Free Software 
 Foundation, either version 3 of the License, or (at your option) any later
 version. 
 
 This program is distributed in the hope that it will be useful, but WITHOUT ANY
 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 PARTICULAR PURPOSE.  See the GNU General Public License for more details. 
 
 You should have received a copy of the GNU General Public License along with
 this program.  If not, see http://www.gnu.org/licenses.

*/
package audiometria;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Random;

/**
 * This class raffles the sound samples used in the test. It receives feedback 
 * from user input for optimize the number of samples neede to perform the
 * whole test. <br />
 * 
 * <li> If user heard a sound at frequency f and amplitude A, only the 
 *      amplitudes &lt A will be outputed next for frquency f.</li>
 * 
 * <li> If user didn't heard a sound at frequency f and amplitude A, only the 
 *      amplitudes &gt A will be outputed next for frquency f.</li>
 * 
 * @author marcos
 */
public class Audiometry {

    public static float lin2dB(float linAmp) {
        return (float) (Math.log10(linAmp)*20);
    }
    
    public static float dB2lin(float db) {
        return (float) (Math.pow(10,db/20));
    }
    
    
    static public float defaultFrequencies[] = {250, 500, 1000, 2000, 4000, 8000};
    //static public float defaultAmplitudes[] = {.1f, .2f, .3f, .4f, .5f, .6f, .7f, .8f, .9f, 1.f};
    static public float defaultAmplitudes[] = {0, -6, -12, -18, -24, -30, -36, -42, -48, -54, -60};

    public Audiometry() {
        this(defaultFrequencies, defaultAmplitudes);
    }

    public Audiometry(float[] frequencies, float[] amplitudes) {
        this.amplitudes = amplitudes;
        this.frequencies = frequencies;
        
        soundSamplesLeft = new HashMap<>(frequencies.length);
        Arrays.sort(amplitudes);
        ArrayList<Float> ampList = new ArrayList<>(amplitudes.length);
        for (float a : amplitudes) {
            ampList.add(a);
        }

        for (float f : frequencies) {
            soundSamplesLeft.put(f, new ArrayList<>(ampList));
        }

        soundSamplesRight = new HashMap<>(soundSamplesLeft);
        leftEarThreshold = new HashMap<>(frequencies.length);
        rightEarThreshold = new HashMap<>(frequencies.length);
    }

    public SoundSample getSoundSample() {
        HashMap<Float, ArrayList<Float>> samples;
        boolean isLeft;

        if (soundSamplesLeft.isEmpty() && soundSamplesRight.isEmpty()) {
            return null;
        }

        if (soundSamplesRight.isEmpty() || (!soundSamplesLeft.isEmpty() && random.nextInt() % 2 == 0)) {
            isLeft = true;
            samples = soundSamplesLeft;
        } else {
            isLeft = false;
            samples = soundSamplesRight;
        }

        int fi = random.nextInt(samples.size());
        float f = (float) (samples.keySet().toArray()[fi]);
        ArrayList<Float> amps = samples.get(f);
        int ai = random.nextInt(amps.size());
        float a = amps.get(ai);

        return new SoundSample(f, a, isLeft);
    }

    public void giveFeedback(SoundSample sound, boolean heard) {

        HashMap<Float, ArrayList<Float>> samples;
        HashMap<Float, Float> threshold;
        if (sound.isLeftChannel) {
            samples = soundSamplesLeft;
            threshold = leftEarThreshold;
        } else {
            samples = soundSamplesRight;
            threshold = rightEarThreshold;
        }

        ArrayList<Float> amps = samples.get(sound.frequency);
        int from;
        int to;
        if (heard) {
            threshold.put(sound.frequency, sound.amplitude);
            from = 0;
            to = amps.indexOf(sound.amplitude);
        } else {
            from = amps.indexOf(sound.amplitude) + 1;
            to = amps.size();
        }
        
        amps = new ArrayList<>(amps.subList(from, to));

        if (amps.isEmpty()) {
            samples.remove(sound.frequency);
        } else {
            samples.put(sound.frequency, amps);
        }
    }

    /**
     * Utility simple class to hold sound parameters.
     */
    public static class SoundSample {

        public float frequency;
        public float amplitude;
        public boolean isLeftChannel;

        public SoundSample(float frequency, float amplitude, boolean isLeftChannel) {
            this.frequency = frequency;
            this.amplitude = amplitude;
            this.isLeftChannel = isLeftChannel;
        }
    }

    /**
     * This thresholds are the minimum *heard* sounds for the left ear;
     */
    public HashMap<Float, Float> getLeftEarThreshold() {
        return leftEarThreshold;
    }

    /**
     * This thresholds are the minimum *heard* sounds for the right ear;
     */
    public HashMap<Float, Float> getRightEarThreshold() {
        return rightEarThreshold;
    }

    public float[] getAmplitudes() {
        return amplitudes;
    }

    public float[] getFrequencies() {
        return frequencies;
    }
    
    protected float[] frequencies;
    protected float[] amplitudes;
    
    private HashMap<Float, ArrayList<Float>> soundSamplesLeft;
    private HashMap<Float, ArrayList<Float>> soundSamplesRight;
    private HashMap<Float, Float> leftEarThreshold; // this thresholds are the minimum *heard* sounds
    private HashMap<Float, Float> rightEarThreshold;
    Random random = new Random();
}
